home *** CD-ROM | disk | FTP | other *** search
/ Aminet 45 / Aminet 45 (2001)(GTI - Schatztruhe)[!][Oct 2001].iso / Aminet / game / role / ldmud-3.2-bin.lha / mud / doc / LPC / closures < prev    next >
Text File  |  2000-07-02  |  22KB  |  507 lines

  1. CONCEPT
  2.         closures
  3.  
  4. NOTE
  5.         This is the official man page concerning closures. If you find
  6.         it hard to read maybe the Closure Guide is an easier introduction
  7.         for you: See closure_guide(LPC) and closures-example(LPC).
  8.  
  9. DESCRIPTION
  10.         Closures provide a means of creating code dynamically and
  11.         passing pieces of code as parameters, storing them in
  12.         variables. One might think of them as a very advanced form of
  13.         process_string(). However, this falls short of what you can
  14.         actually do with them.
  15.  
  16.         The simplest kind of closures are efuns, lfuns or operators.
  17.         For example, #'this_player is an example of a closure. You can
  18.         assign it to a variable as in
  19.  
  20.                 closure f;
  21.                 object p;
  22.                 f = #'this_player;
  23.  
  24.         and later use either the funcall() or apply() efun to evaluate
  25.         it. Like
  26.  
  27.                 p = funcall(f);
  28.  
  29.         or
  30.  
  31.                 p = apply(f);
  32.  
  33.         In both cases there p will afterwards hold the value of
  34.         this_player().  Of course, this is only a rather simple
  35.         application.
  36.  
  37.         Inline closures are a variant of lfun closures, the difference being
  38.         that the function text is written right where the closure is used,
  39.         enclosed in a pair of '(:' and ':)'. The compiler will then take care
  40.         of creating a proper lfun and lfun-closure. The arguments passed to
  41.         such an inline closure are accessible by position: $1 would be the
  42.         first argument, $2 the second, and so on. With this, the above example
  43.         would read:
  44.  
  45.           int * bar() {
  46.             return filter_array(({ 10,50,30,70 }), (: ($1 * 2) > 42 :));
  47.           }
  48.  
  49.         or alternatively:
  50.  
  51.           int * bar() {
  52.             return filter_array(({ 10,50,30,70 }), (: return ($1 * 2) > 42; :));
  53.           }
  54.  
  55.         The difference between the two versions is that in the first form the
  56.         text of the inline closure must be an expression only, whereas in the
  57.         second form any legal statement is allowed. The compiler distinguishes
  58.         the two forms by the last character before the ':)': if it's a ';' or
  59.         '}', the compiler treats the closure as statement(s), otherwise as
  60.         expression.
  61.  
  62.         Inline closures may also nested, so that the following (not very
  63.         useful) example is legal, too:
  64.  
  65.           return filter_array( ({ 10, 50, 30, 70 })
  66.                              , (: string *s;
  67.                                   s = map_array(users(), (: $1->query_name() :));
  68.                                   return s[random(sizeof(s))] + ($1 * 2);
  69.                                 :));
  70.  
  71.         The notation of inline closures is modelled after the MudOS
  72.         functionals, but there are a few important differences in behaviour.
  73.  
  74.         More useful instances of closures can be created
  75.         using the lambda() efun. It is much like the lambda function
  76.         in LISP. For example, you can do the following:
  77.  
  78.                 f = lambda( ({ 'x }), ({ #'environment, 'x }) );
  79.  
  80.         This will create a lambda closure and assign it to f. The
  81.         first argument to lambda is an array describing the arguments
  82.         (symbols) passed to the closure upon evaluation by funcall()
  83.         or apply(). You can now evaluate f, for example by means of
  84.         funcall(f,this_object()). This will result in the following
  85.         steps:
  86.  
  87.                 1. The value of this_object() will be bound to symbol x.
  88.                 2. environment(x) evaluates to environment(this_object())
  89.                    and is returned as the result of the funcall().
  90.  
  91.         One might wonder why there are two functions, funcall() and
  92.         apply(), to perform the seemingly same job, namely evaluating
  93.         a closure. Of course there is a subtle difference. If the last
  94.         argument to apply() is an array, then each of its elements
  95.         gets expanded to an additional paramater. The obvious use
  96.         would be #'call_other as in:
  97.  
  98.                 mixed eval(object ob,string func,mixed *args) {
  99.                   return apply(#'call_other,ob,func,args);
  100.                 }
  101.  
  102.         This will result in calling
  103.         ob->func(args[0],args[1],...,args[sizeof(args)-1]).  Using
  104.         funcall() instead of apply() would have given us
  105.         ob->func(args).
  106.  
  107.         Of course, besides efuns there are closures for operators,
  108.         like #'+, '-, #'<, #'&&, etc.
  109.  
  110.         Well, so far closures have been pretty much limited despite
  111.         their obvious flexibility. This changes now with the
  112.         introduction of conditional and loop operators. For example,
  113.         try:
  114.  
  115.                 closure max;
  116.                 max = lambda( ({ 'x, 'y }),
  117.                               ({ #'? ,({ #'>, 'x, 'y }), 'x, 'y }) );
  118.                 return funcall(max,7,3);
  119.  
  120.         The above example will return 7. What happened? Of course #'?
  121.         is the conditional operator and its 'syntax' is as follows:
  122.  
  123.                 ({ #'?, cond1, val1, cond2, val2, ..., condn, valn,
  124.                   valdefault });
  125.  
  126.         It evaluates cond1, cond2, ..., condn successively until it
  127.         gets a nonzero result and then returns the corresponding
  128.         value. If there is no condition evaluating to a nonzero
  129.         result, valdefault gets returned. If valdefault is omitted, 0
  130.         gets returned. #'?! works just like #'?, except that the !
  131.         operator is applied to conditions before testing. Therefore,
  132.         while #'? is somewhat like an if statement, #'?! resembles an
  133.         if_not statement if there were one.
  134.  
  135.         There are also loops:
  136.  
  137.                 ({ #'do, loopbody1, ..., loopbodyN, loopcond, loopresult })
  138.  
  139.         will evaluate the loopbodies until loopcond evaluates to 0 and
  140.         then return the value of loopresult. Symbols my be used as
  141.         variables, of course.
  142.  
  143.                 ({ #'while, loopcond, loopresult, loopbody1, ..., loopbodyN })
  144.  
  145.         works similar but evaluates loopcond before the loopbodies.
  146.  
  147.         The foreach() loop also exists:
  148.  
  149.                 ({ #'foreach, 'var, expr, loopbody1, ..., loopbodyN })
  150.                 ({ #'foreach, ({ 'var1, ..., 'varN}) , expr
  151.                                         , loopbody1, ..., loopbodyN })
  152.  
  153.  
  154.         Now on to a couple of tricky things:
  155.  
  156.         a) How do I write down an array within a lambda closure to
  157.            avoid interpretation as a subclosure?
  158.  
  159.            ({ #'member_array, 'x, ({ "abc", "xyz }) }) will obviously
  160.            result in an error as soon as lambda() tries to interpret
  161.            "abc" as a closure operator. The solution is to quote the
  162.            array, as in: ({ #'member_array, 'x, '({ "abc", "xyz }) }).
  163.            Applying lambda() to this will not result in an error.
  164.            Instead, the quote will be stripped from the array and the
  165.            result regarded as a normal array literal. The same can be
  166.            achieved by using the efun quote(), e.g.:
  167.  
  168.            ({ #'member_array, 'x, quote( ({ "abc", "xyz }) ) })
  169.  
  170.         b) Isn't it a security risk to pass, say, a closure to the
  171.            master object which then evaluates it with all the
  172.            permissions it got?
  173.  
  174.            Luckily, no. Each closure gets upon compilation bound to
  175.            the object defining it. That means that executing it first
  176.            sets this_object() to the object that defined it and then
  177.            evaluates the closure. This also allows us to call lfuns
  178.            which might otherwise be undefined in the calling object.
  179.  
  180.            There is however, a variant of lambda(), called
  181.            unbound_lambda(), which works similar but does not allow
  182.            the use of lfuns and does not bind the closure to the
  183.            defining object. The drawback is that trying to evaluate it
  184.            by apply() or funcall() will result in an error. The
  185.            closure first needs to be bound by calling bind_lambda().
  186.            bind_lambda() normally takes one argument and transforms an
  187.            unbound closure into a closure bound to the object
  188.            executing the bind_lambda().
  189.  
  190.            Privileged objects, like the master and the simul_efun
  191.            object (or those authorized by the privilege_violation()
  192.            function in the master) may also give an object as the
  193.            second argument to bind_lambda(). This will bind the
  194.            closure to that object. A sample application is:
  195.  
  196.            dump_object(ob)
  197.            // will dump the variables of ob to /dump.o
  198.            {
  199.              closure save;
  200.              save = unbound_lambda( ({ }),
  201.                                     ({ #'save_object, "/open/dump" }) );
  202.              bind_lambda(save,ob);
  203.              funcall(save);
  204.            }
  205.  
  206.            bind_lambda() can also be used with efun closures.
  207.  
  208.         c) It might be an interesting application to create closures
  209.            dynamically as an alternative to writing LPC code to a file
  210.            and then loading it.  However, how do I avoid doing exactly
  211.            that if I need symbols like 'x or 'y?
  212.  
  213.            To do that one uses the quote() efun. It takes a string as
  214.            its argument and transforms it into a symbol. For example,
  215.            writing quote("x") is exactly the same as writing 'x.
  216.  
  217.         d) How do I test if a variable holds a closure?
  218.  
  219.            Use the closurep() efun which works like all the other type
  220.            testing efuns. For symbols there is also symbolp()
  221.            available.
  222.  
  223.         e) That means, I can do:
  224.            if (closurep(f)) return funcall(f); else return f; ?
  225.  
  226.            Yes, but in the case of funcall() it is unnecessary. If
  227.            funcall() gets only one argument and it is not a closure it
  228.            will be returned unchanged. So return funcall(f); would
  229.            suffice.
  230.  
  231.         f) I want to use a function in some object as a closure. How do I do
  232.            that?
  233.  
  234.            There are several ways. If the function resides in
  235.            this_object(), just use #'func_name. If not, or if you want
  236.            to create the function dnynamically, use the efun
  237.            symbol_function(). It takes a string as it first and an
  238.            object as its second argument and returns a closure which
  239.            upon evaluation calls the given function in the given
  240.            object (and faster than call_other(), too, if done from
  241.            inside a loop, since function search will be done only when
  242.            calling symbol_function().
  243.  
  244.         g) Can I create efun closures dynamically, too?
  245.  
  246.            Yes, just use symbol_function() with a single argument.
  247.            Most useful for marker objects and the like. But
  248.            theoretically a security risk if not used properly and from
  249.            inside a security relevant object.  Take care, however,
  250.            that, if there is a simul_efun with the same name, it will
  251.            be preferred as in the case of #'function. Use the efun::
  252.            modifier to get the efun if you need it.
  253.  
  254.         h) Are there other uses of closures except using them to store
  255.            code?
  256.  
  257.            Lots. For example, you can use them within almost all of
  258.            the efuns where you give a function as an argument, like
  259.            filter_array(), sort_array() or walk_mapping().
  260.            sort_array(array,#'>) does indeed what is expected. Another
  261.            application is set_prompt(), where a closure can output
  262.            your own prompt based on the current time and other stuff
  263.            which changes all the time.
  264.  
  265.         Finally, there are some special efun/operator closures:
  266.  
  267.         #'[ : indexes an array.
  268.         #'[< : does the same, but starting at the end.
  269.         #'[..] : gets an array and two numbers
  270.                 and returns a sub-array.
  271.         #'[..<] : same as above but the second index is
  272.                 interpreted as counted from the left end.
  273.         #'[<..]  and
  274.         #'[<..<] : should be clear now.
  275.         #'[.. : takes only one index and returns the sub-
  276.                 array from this index to the end.
  277.         #'[<.. : same as above but the index is interpreted
  278.                 as counted from the left end.
  279.         #'({ : puts all arguments into an array.
  280.         #'([ : gets an unquoted (!) array which must include
  281.                 at least one element as argument and returns a mapping of
  282.                 the width of the given array's size with one entry that
  283.                 contains the first element as key and the other elements
  284.                 as values to the key.
  285.  
  286.         #'negate is for unary minus.
  287.         #', may be followed by any number of closures,
  288.         e.g.: ({ (#',),
  289.                  ({#'= 'h, 'a, }), ({#'=, 'a, 'b }), ({#'=, 'b, 'h }) })
  290.         will swap 'a and 'b when compiled and executed.
  291.  
  292.         ------------
  293.  
  294.         An example from Amylaar:
  295.  
  296.         I have tested the replace_program() functionality with one
  297.         example, which I include below. The room is commonly known as
  298.         /room/orc_valley.c .  A prerequisite to make this work is to
  299.         have valued properties in room.c .
  300.  
  301.         The property C_EXTRA_RESET, if defined, is evaluated at reset
  302.         time in the reset() of the used room.c .  Moreover, you need
  303.         a function to query an unprocessed property to use
  304.         orc_valley.c from fortress.c (That is, don't do an automatic
  305.         funcall there.)  If you can't supply such a function, you
  306.         have to set the property "get_orc" at the end of orc_valley.c
  307.         's extra_reset() with: add_prop("get_orc", lambda(0, get_orc)
  308.         ); which will set the property to a function that returns the
  309.         function that is in the variable get_orc at the time you do
  310.         the add_prop() call.
  311.         
  312.         Back to fortress.c : Assume you have the function
  313.         successfully queried and stored in the variable get_orc. Now
  314.         you can compute your extra_reset() function by: get_orc =
  315.         lambda( 0, ({#'funcall, get_orc, 8, 40}) ); which creates the
  316.         usual 8 orcs with an a_chat chance of 40.
  317.         
  318.         Here comes the orc_valley.c source:
  319.         
  320.             ----------- cut here ------- cut here -------- cut here ---
  321.         
  322.         #include "room.h"
  323.         #include "/sys/stdproperties.h"
  324.         #undef EXTRA_RESET
  325.         #define EXTRA_RESET extra_reset();
  326.         
  327.         extra_reset() {
  328.             closure get_orc;
  329.         
  330.             replace_program("room/room"); /* Must come first. */
  331.             get_orc = lambda( ({'num_orcs, 'chat_chance}),
  332.                ({#'?!, ({#'present, "orc", ({#'previous_object}) }),
  333.                    ({#'do,
  334.                        ({#'=, 'orc, ({#'clone_object, "obj/monster"}) }),
  335.                        ({#'=, 'i, 9}),
  336.                        ({#'do,
  337.                            ({#'call_other, 'orc, "set_level",
  338.                                ({#'+, ({#'random, 2}), 1}) }),
  339.                            ({#'call_other, 'orc,
  340.                                ({#'[,
  341.                                    quote(({"set_aggressive",
  342.                                                "set_ac", "set_short",
  343.                                                "set_al", "set_ep", "set_hp",
  344.                                                "set_race",
  345.                                                "set_alias", "set_name"})),
  346.                                   ({#'-=, 'i, 1}) }),
  347.                                ({#'[,
  348.                                    quote(({1, 0, "An orc", -60, 1014,
  349.                                            30, "orc",
  350.                                       "dirty crap", "orc"})), 'i}) }),
  351.                        'i, 0}),
  352.                        ({#'call_other, 'orc, "load_a_chat", 'chat_chance,
  353.                            quote(({ "Orc says: Kill 'em!\n",
  354.                                "Orc says: Bloody humans!\n",
  355.                                "Orc says: Stop 'em!\n",
  356.                                "Orc says: Get 'em!\n",
  357.                                "Orc says: Let's rip out his guts!\n",
  358.                                "Orc says: Kill 'em before they run away!\n",
  359.                                "Orc says: What is that human doing here!\n",
  360.                            })) }),
  361.                        ({#'=, 'n, ({#'*, ({#'random, 3}), 5}) }),
  362.                        ({#'=, 'weapon, ({#'clone_object, "obj/weapon"}) }),
  363.                        ({#'=, 'i, 5}),
  364.                        ({#'do,
  365.                            ({#'call_other, 'weapon,
  366.                                ({#'[,
  367.                                    quote(({ "set_alt_name",
  368.                                        "set_weight", "set_value",
  369.                                        "set_class", "set_name"})),
  370.                                        ({#'-=, 'i, 1}) }),
  371.                                ({#'[,
  372.                                    quote(({ "knife", 1, 8,  5, "knife",
  373.                                        "knife", 1, 15, 7, "curved knife",
  374.                                        "axe",   2, 25, 9, "hand axe",  })),
  375.                                    ({#'+, 'n, 'i}) }) }),
  376.                        'i, 0}),
  377.                        ({#'transfer, 'weapon, 'orc}),
  378.                        ({#'command,
  379.                            ({#'+, "wield ",
  380.                                ({#'call_other, 'weapon,
  381.                                "query_name"}) }), 'orc}),
  382.                        ({#'move_object, 'orc, ({#'previous_object}) }),
  383.                    ({#'-=, 'num_orcs, 1}), 0})
  384.                })
  385.             );
  386.             add_prop("get_orc", get_orc);
  387.             get_orc = lambda( 0, ({#'funcall, get_orc, 2, 50}) );
  388.             add_prop(C_EXTRA_RESET, get_orc);
  389.             funcall(get_orc);
  390.         }
  391.         
  392.         TWO_EXIT("room/slope", "east",
  393.                 "room/fortress", "north",
  394.                 "The orc valley",
  395.                 "You are in the orc valley. This place is inhabited"
  396.                 "by orcs.\n"
  397.                 "There is a fortress to the north, with lot of signs
  398.                 of orcs.\n", 1)
  399.         
  400.             ----------- cut here ------- cut here -------- cut here ----
  401.         
  402.  
  403.         Procedural elements:
  404.         ====================
  405.  
  406.         definition of terms:
  407.           <block>  : zero or more values to be evaluated.
  408.           <test>   : one value to be evaluated as branch or loop condition.
  409.           <result> : one value to be evaluated at the end of the
  410.                      execution of the form; the value is returned.
  411.           <lvalue> : local variable/parameter, global variable, or an
  412.                      indexed lvalue.
  413.         useded EBNF operators:
  414.         { }     iteration
  415.         [ ]     option
  416.  
  417.         forms:
  418.           ({#', <body> <result>})
  419.           ({#'? { <test> <result> } [ <result> ] })
  420.           ({#'?! { <test> <result> } [ <result> ] })
  421.           ({#'&& { test } })
  422.           ({#'|| { test } })
  423.           ({#'while <test> <result> <body>})    loop while test
  424.                                                 evaluates non-zero.
  425.           ({#'do <body> <test> <result>})       loop till test
  426.                                                 evaluates zero.
  427.           ({#'= { <lvalue> <value> } })         assignment
  428.                                                 other assignment
  429.                                                 operators work, too.
  430.  
  431.         lisp similars:
  432.           #',           progn
  433.           #'?           cond
  434.           #'&&          and
  435.           #'||          or
  436.           #'while       do      /* but lisp has more syntactic candy here */
  437.           #'=           setq
  438.  
  439.         A parameter / local variable 'foo' is referenced as 'foo , a
  440.         global variable as ({#'foo}) . In lvalue positions
  441.         (assignment), you need not enclose global variable closures in
  442.         arrays.
  443.  
  444.         Call by reference parameters are given with ({#'&, <lvalue>})
  445.  
  446.         Some special efuns:
  447.         #'[             indexing
  448.         #'[<            indexing from the end
  449.         #'negate        unary -
  450.  
  451.         Unbound lambda closures
  452.         =======================
  453.  
  454.         These closures are not bound to any object. They are created
  455.         with the efun unbound_lambda() . They cannot contain
  456.         references to global variables, and all lfun closures are
  457.         inserted as is, since there is no native object for this
  458.         closure.  You can bind and rebind unbound lambda closures to
  459.         an object with efun bind_lambda() You need to bind it before
  460.         it can be called. Ordinary objects can obly bind to
  461.         themselves, binding to other objects causes a privilege
  462.         violation().  The point is that previous_object for calls done
  463.         from inside the closure will reflect the object doing
  464.         bind_lambda(), and all object / uid based security will also
  465.         refer to this object.
  466.  
  467.  
  468.         The following is mostly vapourware.
  469.  
  470.         Well, another application would be that some things in the
  471.         driver can be, sort of, microprogrammed.  The master object
  472.         could set some hooks in inaugurate_master(), like creating the
  473.         code for move_object(), using a primitive low_move_object() or
  474.         __move_object() or such. All calls of init(), exit(), etc. can
  475.         thus be controlled on mudlib level.  The driver would do an
  476.         implicit bind_lambda() to the victim when the closure is used.
  477.  
  478.         e.g.
  479.         ({#'?, ({#'=, 'ob, ({#'first_inventory, 'destination}) }),
  480.             ({#'do,
  481.                 ({#'call_other, 'ob, "init"}),
  482.             ({#'=, 'ob, ({#'next_inventory, 'ob}) }), 0 })
  483.         })
  484.  
  485.         or
  486.  
  487.         ({#'filter_objects, ({#'all_inventory, 'destination}), "init"})
  488.         /* Won't show init failures due to move/destruct */
  489.  
  490.         is equivalent to
  491.  
  492.         if (ob = first_inventory(destination) ) {
  493.             do {
  494.                 ob->init();
  495.             } while(ob = next_inventory(ob) );
  496.         }
  497.  
  498.         and it's speed is mainly determined by the call_other. Thus,
  499.         it shouldn't be noticably slower than the current C code in
  500.         move_object().
  501.  
  502. AUTHOR
  503.         MacBeth, Amylaar, Hyp
  504.  
  505. SEE ALSO
  506.         closures-abstract(LPC), closures-example(LPC), closure_guide(LPC)
  507.